
#include "CFile.hpp"
#include <fstream>
#include <iostream>
#include <sstream>
#include <memory>
#include <cstdio>
#include <cerrno>
#include <cstring>

namespace CFileLib
{
	void CFile::Open()
	{
		if (opened) Close();
		const char* m = nullptr;
		switch (mode)
		{
		case Mode::ReadOnly: m = binary ? "rb" : "r"; break;
		case Mode::WriteOnly: m = binary ? "wb" : "w"; break;
		case Mode::Append: m = binary ? "ab" : "a"; break;
		default: throw std::invalid_argument("Invalid `mode`.");
		}
		fp = fopen(path.c_str(), m);
		if (!fp)
		{
			std::stringstream ss;
			ss << "Failed to open `" << path << "`: " << strerror(errno);
			throw OpenFileFail(ss.str());
		}
		opened = true;
	}

	void CFile::Open(const std::string& f, bool is_binary, Mode openmode)
	{
		path = f;
		binary = is_binary;
		mode = openmode;
		Open();
	}

	CFile::CFile() :
		path(),
		binary(false),
		mode(CFile::Mode::Unknown),
		opened(false),
		fp(nullptr)
	{
	}

	CFile::CFile(const CFile& f):
		path(f.path),
		binary(f.binary),
		mode(f.mode),
		opened(false),
		fp(nullptr)
	{
		if (f.opened) Open();
	}

	CFile::CFile(const std::string& f, bool binary, Mode mode) :
		path(f),
		binary(binary),
		mode(mode),
		opened(false)
	{
		Open();
	}

	void CFile::Close()
	{
		if (opened)
		{
			fclose(reinterpret_cast<FILE*>(fp));
			fp = nullptr;
			opened = false;
		}
		else throw std::invalid_argument("File is not opened.");
	}

	CFile::~CFile()
	{
		if (opened) Close();
	}

	void CFile::Read(void* buf, size_t read_size)
	{
		if (fread(buf, read_size, 1, reinterpret_cast<FILE*>(fp)) != 1)
		{
			std::stringstream ss;
			ss << "Failed to read `" << path << "`: " << strerror(errno);
			throw ReadFileFail(ss.str());
		}
	}

	void CFile::Write(void* buf, size_t read_size)
	{
		if (fwrite(buf, read_size, 1, reinterpret_cast<FILE*>(fp)) != 1)
		{
			std::stringstream ss;
			ss << "Failed to write `" << path << "`: " << strerror(errno);
			throw WriteFileFail(ss.str());
		}
	}

	void CFile::fgets(char* buf, int bufsize)
	{
		if (!std::fgets(buf, bufsize, reinterpret_cast<FILE*>(fp)))
		{
			std::stringstream ss;
			ss << "Failed to read `" << path << "`: " << strerror(errno);
			throw ReadFileFail(ss.str());
		}
	}

	bool CFile::fputs(const char* buf)
	{
		return std::fputs(buf, reinterpret_cast<FILE*>(fp)) ? true : false;
	}

	bool CFile::fputs(const std::string& s)
	{
		return fputs(s.c_str());
	}

	bool CFile::feof()
	{
		return std::feof(reinterpret_cast<FILE*>(fp));
	}
}
